<?php

namespace App\Models\Common\Area\Repositories;

use App\Models\Common\Area\Area;
use App\Models\Common\Area\Repositories\Interfaces\AreaRepositoryInterface;
use App\Trip\Repository\BaseRepository;
use Exception;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Str;
use JetBrains\PhpStorm\Pure;

class AreaRepository extends BaseRepository implements AreaRepositoryInterface
{
    #[Pure] public function __construct(Area $model)
    {
        parent::__construct($model);
    }

    public function addArea(array $data): Area
    {
        if (!Schema::hasColumn($this->model->getTable(), 'center')) {
            unset($data['center']);
        }
        return $this->create($data);
    }

    public function updateArea(Area $area, array $data): bool
    {
        if (!Schema::hasColumn($this->model->getTable(), 'center')) {
            unset($data['center']);
        }
        return $this->update($data, $area);
    }

    /**
     * @param string $citycode
     * @param bool   $municipality 是否直辖市
     * @return Area|null
     */
    public function findCityByCode(string $citycode, bool $municipality = false): ?Area
    {
        // 相当于 区号
        if (mb_strlen($citycode) <= 4) {
            $city = $this->findOneBy([
                'citycode' => $citycode,
                'level'    => 'city',
            ]);

            if (!$city) {
                $city = $this->findOneBy([
                    'citycode' => $citycode,
                    'level'    => 'province',
                ]);
            } elseif ($municipality) {
                $cp = $this->findByCode($city->getParentCode());
                if ($cp && $cp->isMunicipality()) {
                    return $cp;
                }
            }
            return $city;
        } else {
            return $this->findByCode($citycode);
        }
    }

    public function findOneBy(array $data): ?Area
    {
        $cache_key = "Area:findOneBy:".sha1(json_encode($data));
        if (Cache::has($cache_key)) {
            return Cache::get($cache_key);
        }
        $result = parent::findOneBy($data);
        Cache::put($cache_key, $result, config('trip.ttl.minutes') * 10);
        return $result;
    }

    public function findByCode(string $adcode, bool $refresh = false): ?Area
    {
        $cache_key = "Area:code:{$adcode}";
        if (Cache::has($cache_key) && !$refresh) {
            return Cache::get($cache_key);
        }
        $area = $this->findOneBy([
            'adcode' => $adcode,
        ]);

        $area && Cache::forever($cache_key, $area);
        return $area;
    }

    /**
     * @throws Exception
     */
    public function fuzzyFind(string $name, string|int $level, string $parent_code = ''): ?Area
    {
        if (Str::length($name) < 2) {
            throw new Exception("模糊匹配至少两个字~~");
        }

        $isDistrict = ($level == 'district' || $level == Area::DISTRICT_LEVEL);

        return $this->model->where('name', 'like', $name."%")
                           ->where('level', $level)
                           ->when($parent_code, function ($q) use ($parent_code, $isDistrict) {
                               if ($isDistrict) {
                                   $prefix_two = mb_substr($parent_code, 0, 2);
                                   $q->where('parent_code', 'like', "{$prefix_two}%");
                               } else {
                                   $q->where('parent_code', $parent_code);
                               }
                           })
                           ->first();
    }

    public function findByName(string $name): ?Area
    {
        return $this->findOneBy([
            'name' => $name,
        ]);
    }

    public function listProvinces(bool $refresh): Collection
    {
        $ck = "Area:listProvinces";
        if (Cache::has($ck)) {
            return Cache::get($ck);
        }
        $lists = $this->findBy(['level' => 'province']);
        $lists->count() && Cache::forever($ck, $lists,);
        return $lists;
    }
}